package com.damon.thread;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.sql.SQLException;
import java.util.ArrayList;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

import com.damon.JDBC.MonitorDeviceJDBC;
import com.damon.common.ConstantObject;
import com.damon.entity.DVCEntity;
import com.damon.entity.MonitorConfigByBitEntity;
import com.damon.entity.MonitorDeviceEntity;
import com.damon.log.WcsLog;
import com.damon.utils.ByteChangeUtil;
import com.damon.utils.GlobalOjbectUtil;
import com.damon.utils.LogUtil;
/**
 * 	该类是socket发送接收信息的类。
 * 	比较重要的一个方面是该类嵌套了很多try-catch块。
 * 	其中比较关键的向服务端读取数据的循环里的try块，主要分成两类：
 * 	一类是会使socket连接断开的异常，该类异常是由socket发出的io流异常，该异常通过while循环外的try块处理，
 * 		可以达到在io断开时，socket跳出内层业务循环，在外层连接循环中，一直尝试连接，直至连接成功！
 * 	其他类异常，都会在业务循环内处理，不会使socket断开，目前包括：sql异常、运行时异常、sleep方法异常
 * @author 风蚕
 *
 */
public class SocketThread extends Thread{
//	Logger logger = LoggerFactory.getLogger(PrintSocketThread.class);
	WcsLog logger;
	DVCEntity deviceInfo;
	boolean handshake;//握手协议是否完成
	String printDevInfo;//一段用于打印该设备信息的字符串
	long start;
	public SocketThread() {}
	
	public SocketThread(DVCEntity printDeviceEntity) {
		this.deviceInfo = printDeviceEntity;
		this.logger = LogUtil.getLogger(SocketThread.class, deviceInfo);
		this.handshake = false;
		this.printDevInfo = "["+deviceInfo.getDeviceNumber()+"] ["+deviceInfo.getIp()+":"+deviceInfo.getPort()+"]";
		
	}
	
	@Override
	public void run() {
//		String devNum = deviceInfo.getDeviceNumber();
		String ip = deviceInfo.getIp();
		int port = deviceInfo.getPort();
//		String devInfo = "["+devNum+"] ["+ip+":"+port+"]";
		Socket socket = null;
		OutputStream os = null;
		InputStream is = null;
		while(true) {
			try {
//				logger.info("开始为：["+ip+":"+port+"]建立连接！");
				
				socket = new Socket(ip,port);
				
				//设置socket3秒钟未读到数据，抛出异常，重新连接，防止无限等待
				socket.setSoTimeout(3000);
				
				os = socket.getOutputStream();
				is = socket.getInputStream();
				logger.info("连接成功！");
				//用于打印已连接设备信息
				GlobalOjbectUtil.getConnecteDDeviceSet().add(printDevInfo);
				GlobalOjbectUtil.getWindow().getDeviceStatusLabel()
								//.setText("已连接设备："+GlobalOjbectUtil.getConnecteDDeviceSet());
								.setText("客户端数量："+GlobalOjbectUtil.getConnecteDDeviceSet().size());
//				handshake=true;//本机测试时，不握手
				while(handshake==false) {//未成功进行两次握手时，循环进行握手协议
					handshake = handshake(is, os);
					Thread.sleep(1000);
				}
				logger.info("握手协议正确结束！");
//				try {
					//该嵌套循环用于执行业务：循环查找任务表，并发给打印机，最后更新打印状态
					while(true) {
						start = System.currentTimeMillis();
						try {
							//***************这里是业务*****************
							//一部分特殊数据按一个或多个字节来取值,只在monitor_static中运行
							if("monitor_static".equals(deviceInfo.getDeviceNumber())) {
								//按byte来更新数据
								updateMonitorStatic(is, os);
							}
							//通用数据在这里更新，会根据不同的monitorNo在不同线程中运行
							updateMonitorByBit(is, os);
							
						} 
						catch (SQLException e) {
							logger.error("向表中更新数据数数据库错误！",e);
							e.printStackTrace();
						}			
						catch (RuntimeException e) {
							logger.error("运行时异常，"+e.getMessage(),e);
							e.printStackTrace();
						}
						finally {
							long end = System.currentTimeMillis();
							logger.info("耗时："+(end-start)+"ms!!!\n");
						}
						
						
						try {
							//业务阻塞，循环读取业务表的间隔
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							logger.error("在进行握手时，sleep函数错误！",e);
							e.printStackTrace();
						}
						
						
					}
			} 
			catch (SocketTimeoutException e) {
				logger.error("io流read读取超时！",e);
				e.printStackTrace();
			}
			catch (IOException e) {
				logger.error("连接建立失败，请检测设备是否正常开启！",e);
				e.printStackTrace();
			} 
			catch (DecoderException e) {
				logger.error("在进行握手时，16进制相关操作失败！",e);
				e.printStackTrace();
			}
			catch (InterruptedException e) {
				logger.error("在进行握手时，sleep函数错误！",e);
				e.printStackTrace();
			}


			finally {
				//改变握手状态为false
				handshake = false;
				//用于向状态栏更已连接设备信息
				GlobalOjbectUtil.getConnecteDDeviceSet().remove(printDevInfo);
				GlobalOjbectUtil.getWindow().getDeviceStatusLabel()
//								.setText("已连接设备："+GlobalOjbectUtil.getConnecteDDeviceSet());
				.setText("客户端数量："+GlobalOjbectUtil.getConnecteDDeviceSet().size());
				logger.error("设备已断开！\n");
				try {
					if(os != null) {
						os.close();
					}
					if(is != null) {
						is.close();
					}
					if (socket != null) {
						socket.close();
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			//打印机重连时间：1s
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				logger.error("设备重连时，线程阻塞sleep方法产生错误！",e);
				e.printStackTrace();
			}
		}

	}
	
	/**
	 * 一部分不规律的数据，单独去解析
	 * @param is
	 * @param os
	 * @throws IOException
	 * @throws DecoderException
	 * @throws SQLException
	 */
	public void updateMonitorStatic(InputStream is,OutputStream os) throws IOException, DecoderException, SQLException {
		ArrayList<MonitorDeviceEntity> deviceEntitieList = new ArrayList<>();
		
		//获取系统参数
		byte[] dataBytes = getDataBytes(is, os, 27,1000,0);
		
		//环线速度
		byte[] systemSpeedByte = new byte[2];
		System.arraycopy(dataBytes, 0, systemSpeedByte, 0, 2);
//		logger.info("环线速度字节数组："+Arrays.toString(systemSpeedByte));
//		logger.info("环线速度hex:"+Hex.encodeHexString(systemSpeedByte));
		String speedBinaryStr =  ByteChangeUtil.bytesTobinaryString(systemSpeedByte);
		float speed = (float) (Integer.parseInt(speedBinaryStr, 2)/1000.000);
//		logger.info("环线速度:"+speed);
		MonitorDeviceEntity speedDeviceEntity = new MonitorDeviceEntity();
		speedDeviceEntity.setDeviceType("13");
		speedDeviceEntity.setDeviceCode("1");
		speedDeviceEntity.setDeviceState(String.valueOf(speed));
		deviceEntitieList.add(speedDeviceEntity);
		
		//系统运行状态
		byte systemState = dataBytes[26];
//		logger.info("系统运行状态:"+systemState);
		MonitorDeviceEntity systemStatEntityDeviceEntity = new MonitorDeviceEntity();
		systemStatEntityDeviceEntity.setDeviceType("13");
		systemStatEntityDeviceEntity.setDeviceCode("2");
		systemStatEntityDeviceEntity.setDeviceState(String.valueOf(systemState));
		deviceEntitieList.add(systemStatEntityDeviceEntity);
//		
		//系统参数
		MonitorDeviceJDBC.updateAll(deviceEntitieList);
		
		/* *************输送线是否掉栈*************** */
		
		//实际数据字节数组
		byte[] transportLineDataBytes = getDataBytes(is, os, 1,1004,5);
		// 获取连续的字节数组的二进制字符串
		String bitStr = getBitString(transportLineDataBytes);
//		logger.info("输送线掉栈信息，二进制字符串："+bitStr);
		//该list用于保存输送线掉栈的数据
		ArrayList<MonitorDeviceEntity> deviceEntityList = new ArrayList<>();
		
		int[] bit0arrs = {1,2,3,4,5,6,7,19,20,23,24};
		int[] bit1arrs = {8,9,10,11,12,13,14,15,16,17,18,21,22};
		
		//将需要更新的数据保存到list中，一次更新
		for (int code:bit0arrs) {
			MonitorDeviceEntity deviceInfo = new MonitorDeviceEntity();
			deviceInfo.setDeviceState(String.valueOf(bitStr.charAt(0)));
			deviceInfo.setDeviceCode(String.valueOf(code));
			deviceInfo.setDeviceType("8");
			deviceEntityList.add(deviceInfo);
		}
		for (int code:bit1arrs) {
			MonitorDeviceEntity deviceInfo = new MonitorDeviceEntity();
			deviceInfo.setDeviceState(String.valueOf(bitStr.charAt(1)));
			deviceInfo.setDeviceCode(String.valueOf(code));
			deviceInfo.setDeviceType("8");
			deviceEntityList.add(deviceInfo);
		}
		MonitorDeviceJDBC.updateMoreByType(deviceEntityList.get(0).getDeviceType(),deviceEntityList);
	}
	
	/**
	 * 	在该方法中，根据表中配置的需要读取的监控设备，遍历数据，表中每一条数据会进行一次socket读取，数据库更新
	 * @param is
	 * @param os
	 * @param config
	 * @throws IOException
	 * @throws DecoderException
	 * @throws SQLException
	 */
	//TODO
	public void updateMonitorByBit(InputStream is, OutputStream os) throws IOException, DecoderException, SQLException {
		
		//遍历每一种设备的配置信息
		for (MonitorConfigByBitEntity config : GlobalOjbectUtil.getConfigList()) {
			
			
			//该list用于保存每一种moniterType的数据
			ArrayList<MonitorDeviceEntity> deviceEntityList = new ArrayList<>();
			
			//如果该条配置的设备号，不是该线程所属的设备号跳过
			if(!deviceInfo.getDeviceNumber().equals(config.getMonitor_No())) {
				continue;
			}
			
//			logger.info(entity.getDeviceDesc()+"*******");
			
			// 获取数据byte数组
			byte[] dataBytes = getDataBytes(is, os, config);
//			logger.info("获取字节数组用时："+(System.currentTimeMillis()-start));
			
			// 获取连续的字节数组的二进制字符串
			String bitStr = getBitString(dataBytes);
//			logger.info(entity.getDeviceDesc()+"二进制数组字符串：" + bitStr);

			//将需要更新的数据保存到list中，一次更新
			for (int i = 0; i < bitStr.length(); i++) {
				MonitorDeviceEntity deviceInfo = new MonitorDeviceEntity();
				deviceInfo.setDeviceState(String.valueOf(bitStr.charAt(i)));
				deviceInfo.setDeviceCode(String.valueOf(i + 1));
				deviceInfo.setDeviceType(config.getDeviceType());
				deviceEntityList.add(deviceInfo);
			}
			//向表中更新该设备信息
			if(deviceEntityList.size()>0) {
				MonitorDeviceJDBC.updateMoreByType(deviceEntityList.get(0).getDeviceType(), deviceEntityList);
			}
		}
//		logger.info("获取数据用时："+(System.currentTimeMillis()-start)+"****ms!");
//		long upStart = System.currentTimeMillis();
//		MonitorDeviceJDBC.updateAll(deviceEntityList);
//		logger.info("更新数据用时："+(System.currentTimeMillis()-upStart)+"****ms!");	
	}
	
	/**
	 * 	根据配置表中的db块信息，想socket服务端请求数据。
	 * 		最终返回对应每一个设备号的字节数组
	 * @param is
	 * @param os
	 * @param readByteLeng
	 * @param dbNo
	 * @param startBit
	 * @return
	 * @throws IOException
	 * @throws DecoderException
	 */
	public byte[] getDataBytes(InputStream is,OutputStream os,int readByteLeng,int dbNo,int startBit) throws IOException, DecoderException {
		
		// 根据配置参数，组装向plc请求的报文
		String sendHexMsg = createSendMsg(readByteLeng, dbNo,startBit);
//		logger.info("sentMsg:"+sendHexMsg);
		
		// 向plc发送读取db块请求，并获取返回数据的字节码数组
		byte[] replyBytes = onceSocketCommunication(is, os, Hex.decodeHex(sendHexMsg));
//		logger.info("reply: "+Hex.encodeHexString(replyBytes));
		// 将plc返回的字节数组，去除多余的字节，
		// 包括前部多余的字节，及实际数据后的补0字节
		int dataSizeLen = replyBytes.length - 25;//实际数据字节长度
		byte[] dataBytes = new byte[dataSizeLen];
		System.arraycopy(replyBytes, 25, dataBytes, 0, dataSizeLen);

		return dataBytes;
		
	}
	/**
	 * 	根据配置表中的db块信息，想socket服务端请求数据。
	 * 		最终返回对应每一个设备号的字节数组
	 * @param is
	 * @param os
	 * @param readByteLeng
	 * @param dbNo
	 * @param startBit
	 * @return
	 * @throws IOException
	 * @throws DecoderException
	 */
	public byte[] getDataBytes(InputStream is,OutputStream os,MonitorConfigByBitEntity config) throws IOException, DecoderException {
		
		// 根据配置参数，组装向plc请求的报文
		String sendHexMsg = createSendMsg(config.getByteLength(), config.getDbNo(),config.getOffsetByte());
//		logger.info("sentMsg:"+sendHexMsg);
		
		// 向plc发送读取db块请求，并获取返回数据的字节码数组
		byte[] replyBytes = onceSocketCommunication(is, os, Hex.decodeHex(sendHexMsg));
//		logger.info("reply: "+Hex.encodeHexString(replyBytes));
		// 将plc返回的字节数组，去除多余的字节，
		// 包括前部多余的字节，及实际数据后的补0字节
		int dataSizeLen = replyBytes.length - 25;//实际数据字节长度
		byte[] dataBytes = new byte[dataSizeLen];
		System.arraycopy(replyBytes, 25, dataBytes, 0, dataSizeLen);
		
		return dataBytes;
		
	}

	
	/**
	 * 	该方法把字节数组转化成二进制字符串。
	 * 	根据db块存放数据的方式，需要把没一字节的里8位数据整体颠倒一下，
	 * 		最终的二进制字符串数据才会对应到每种设备的deviceCode。
	 * @param dataBytes
	 * @return
	 */
	public String getBitString(byte[] dataBytes) {
		StringBuffer bitStringBuffer = new StringBuffer();
		
		for(int i=0;i<dataBytes.length;i++) {
			//当前byte
			byte curByte = dataBytes[i];
			
			//当前byte转化成二进制
			String bitStr = ByteChangeUtil.byteToBinaryStr(curByte);
			
			//当前byte中的8位，颠倒顺序
			StringBuffer buf = new StringBuffer();
			bitStr = buf.append(bitStr).reverse().toString();
			
			//保存颠倒后的二进制字符串
			bitStringBuffer.append(bitStr);
		}
		return bitStringBuffer.toString();
	}
	
	/**
	 * 
	 * @param readSize	要读的长度，单位是byte
	 * @param DBNumber	DB块名称
	 * @param startByte	从哪里开始读取，单位是bit，取字节的8倍。
	 * @return
	 */
	public String createSendMsg(int readSize,int DBNumber,int startByte) {
//		logger.info("配置："+readSize+" db:"+DBNumber+" startbit:"+startByte);
		String readSizeHex = intChangeToHexString(readSize);
		String DBNumberHex = intChangeToHexString(DBNumber);
		String startByteHex = intChangeToHexString(startByte*8);
//		logger.info("hex配置：sizeLen:"+readSizeHex+" db"+DBNumberHex+" start:"+startByteHex);
		String resultMsg = "0300001F02F080320100000000000E00000401120A1002"+readSizeHex+DBNumberHex+"8400"+startByteHex;
		return resultMsg;
	}
	
	/**
	 * 	对int转化成16进制后的字符串补0
	 * 	将小于65535的int数字，转化为4个长度的16进制字符串，如3（int）转化为0003（String）
	 * @param number
	 * @return
	 */
	public String intChangeToHexString(int number) {
		if(number > 65535) {
			logger.error("传入参数大于65535,超出两个字节长度！");
			throw new RuntimeException("传入参数大于65535,超出两个字节长度！");
		}
		String hexString = Integer.toHexString(number);
		hexString = hexString.length()==1?"000"+hexString:hexString;
		hexString = hexString.length()==2?"00"+hexString:hexString;
		hexString = hexString.length()==3?"0"+hexString:hexString;
		return hexString;
	}
	
	/*
	 * 	与西门子opc建立通讯的2次握手协议
	 */
	public boolean handshake(InputStream is,OutputStream os) throws IOException, DecoderException {
		//第一次握手
		logger.info("第一次握手开始");
		byte[] onecBytes = onceSocketCommunication(is, os, Hex.decodeHex(ConstantObject.HANDSHAKE_PROTOCOL_SEND_ONCE));
		String onecReply = new String(Hex.encodeHex(onecBytes)).toUpperCase();
		logger.info("第一次握手，回复信息："+onecReply);
		
		//第二次握手
		logger.info("第二次握手开始");
		byte[] twiceBytes = onceSocketCommunication(is, os, Hex.decodeHex(ConstantObject.HANDSHAKE_PROTOCOL_SEND_TWICE));
		String twiceReply = new String(Hex.encodeHex(twiceBytes)).toUpperCase();
		logger.info("第二次握手，回复信息："+twiceReply);
		return true;
	}
	/*
	 * 	向socket服务端发送一次报文，并等待该次报文的回复。
	 */
	public byte[] onceSocketCommunication(InputStream is,OutputStream os,byte[] sendBytes) throws IOException {
		os.write(sendBytes);
//		Thread.sleep(10);
		byte[] bytes = new byte[ConstantObject.REPLY_BYTE_SIZE];
		int len = is.read(bytes);
		if(len == -1) {
			logger.error("服务端断开,主动抛出异常！！");
			throw new RuntimeException("服务端断开,主动抛出异常！！");
//			return null;
		}
//		System.out.println("数据："+new String(Hex.encodeHex(bytes)));
//		System.out.println(len);
		byte[] cutZeroBytes = new byte[len];// 存放去除多余零后的字节数组
		//无法确定socket服务端发送的具体报文大小，字节数组必须尽可能的大。因此字节数组末尾会产生多余的0。
		// 字节数组去零
		System.arraycopy(bytes, 0, cutZeroBytes, 0, len);
		return cutZeroBytes;
	}
}
